package edu.neu.cs6515.net;

import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBException;

import com.google.common.collect.Lists;
import com.google.java.contract.ContractAssertionError;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import edu.neu.cs6515.player.IPlayer;
import edu.neu.cs6515.player.strategy.StrategyException;
import edu.neu.cs6515.turn.ITurnProxy;
import edu.neu.cs6515.util.FunctNoRet;
import edu.neu.cs6515.util.FunctTwoParams;
import edu.neu.cs6515.xml.Board;
import edu.neu.cs6515.xml.False;
import edu.neu.cs6515.xml.IngeniousColor;
import edu.neu.cs6515.xml.Placement;
import edu.neu.cs6515.xml.Score;
import edu.neu.cs6515.xml.Tile;
import edu.neu.cs6515.xml.w12.Accept;
import edu.neu.cs6515.xml.w12.End;
import edu.neu.cs6515.xml.w12.PlayerName;
import edu.neu.cs6515.xml.w12.PlayerNames;
import edu.neu.cs6515.xml.w12.Rerack;
import edu.neu.cs6515.xml.w12.Rerackable;
import edu.neu.cs6515.xml.w12.True;
import edu.neu.cs6515.xml.w12.TurnResult;
import edu.neu.cs6515.xml.w12.XMLTurn;

/**
 * the Player class serves as the proxy of another Player
 * 
 */
public class PlayerProxy implements IPlayer {

    private final String playerName;

    private final IProxyInOutput inOut;

    private boolean endTurn = false;

    private Map<Class, FunctTwoParams<Object, ITurnProxy>> handlers;

    @Getter
    private int nbrOfPlayers;

    /**
     * C'tor
     * 
     * @param playerName
     *            the name of the player
     * @param inOut
     *            input and output broker for the player
     */
    public PlayerProxy(final String playerName,
            final IProxyInOutput inOut) {
        this.playerName = playerName;
        this.inOut = inOut;
        handlers = new HashMap<Class, FunctTwoParams<Object, ITurnProxy>>();
        handlers.put(Placement.class, new PlacementHandler());
        handlers.put(Rerackable.class, new RerackableHandler());
        handlers.put(Rerack.class, new RerackHandler());
        handlers.put(End.class, new EndHandler());
    }

    /**
     * Placement message handler, apply the placement to turnproxy
     */
    private class PlacementHandler implements FunctTwoParams<Object, ITurnProxy> {
        @Override
        public void exec(final Object a, final ITurnProxy turnproxy) {
            Placement placement = (Placement) a;
            List<Tile> newTile = turnproxy.placeTile(placement);
            if (newTile.isEmpty()) {
                respond(new False());
            } else {
                respond(newTile.get(0));
            }
        }
    }

    /**
     * ask turn proxy if rerackable or not
     */
    private class RerackableHandler implements FunctTwoParams<Object, ITurnProxy> {
        @Override
        public void exec(final Object a, final ITurnProxy proxy) {
            if (proxy.isReRackable()) {
                respond(new True());
            } else {
                respond(new False());
            }
        }
    }

    /**
     * ask the turnproxy to rerack
     */
    private class RerackHandler implements FunctTwoParams<Object, ITurnProxy> {
        @Override
        public void exec(final Object a, final ITurnProxy turnproxy) {
            if (((Rerack) a).getTile().size() == 0) {
                turnproxy.reRack();
                Rerack rerack = new Rerack(turnproxy.getAllTilesInPlayersHand());
                respond(rerack);
            }
        }
    }

    /**
     * set endTurn flag to true, the player proxy should end the turn
     */
    private class EndHandler implements FunctTwoParams<Object, ITurnProxy> {
        @Override
        public void exec(final Object a, final ITurnProxy proxy) {
            endTurn = true;
        }
    }

    @Override
    public void registerAccepted(
            final int nbrOfPlayers,
            final List<Tile> tiles) {
        this.nbrOfPlayers = nbrOfPlayers;
        Accept accept =
                new Accept(nbrOfPlayers, tiles);
        respond(accept);
    }

    /**
     * respond the object
     */
    private void respond(final Object obj) {
        try {
            System.out.println("respond " + playerName + " with : " + obj);
            inOut.respond(obj);
        } catch (InputOutputException e) {
            e.printStackTrace();
        }
    }

    /**
     * recieve a message from input and output
     * 
     * @return incoming message
     * @throws InputOutputException
     */
    private Object recieve() {
        Object obj = inOut.receive();
        System.out.println("recieve from " + playerName + " : " + obj);
        return obj;
    }

    @Override
    public void makeYourTurn(
            final ITurnProxy turnproxy) {
        endTurn = false;
        XMLTurn turn = new XMLTurn(
                new Board(Lists.newArrayList(turnproxy.getAllPlacementsOnBoard()),
                                        this.getNbrOfPlayers()),
                turnproxy.getCurrentScore(),
                turnproxy.getAllTilesInPlayersHand());
        respond(turn);
        while (!endTurn) {
            Object obj = recieve();
            if (handlers.keySet().contains(obj.getClass())) {
                handlers.get(obj.getClass()).exec(obj, turnproxy);
            } else {
                throw new RuntimeException("illegal message");
            }
        }
    }

    @Override
    public void setPlayersInGame(
            final List<String> playersStartGame) {
        List<PlayerName> listOfNames = new LinkedList<PlayerName>();
        for (String name : playersStartGame) {
            listOfNames.add(new PlayerName(name));
        }
        PlayerNames names = new PlayerNames(listOfNames);
        respond(names);
    }

    @Override
    public void recieveTurnBroadcast(
            final List<Placement> placementsInLastTurn,
            final String playerName) {
        respond(new TurnResult(playerName,
                placementsInLastTurn));
    }

}
